/*******************************************************************************
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *  
 * Copyright (c) 2011 Michael Chan.
 ******************************************************************************/
package com.google.code.solrdimension.queryresponse;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang.StringUtils;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.code.solrdimension.config.DimensionConfigWrapper;
import com.google.code.solrdimension.config.QueryParamConstants;
import com.google.code.solrdimension.config.StaticConfiguration;
import com.google.code.solrdimension.config.dimension.DimensionConfig;
import com.google.code.solrdimension.config.dimensionvalue.AbstractDimensionValueConfig;
import com.google.code.solrdimension.config.dimensionvalue.ExactDimensionValueConfig;
import com.google.code.solrdimension.core.DimensionResponse;
import com.google.code.solrdimension.core.dimensions.Dimension;
import com.google.code.solrdimension.core.dimensions.DimensionValue;
import com.google.code.solrdimension.parsers.exception.SolrDimensionException;
import com.google.code.solrdimension.solrqueryutil.QueryUtil;

/**
 * The {@link QueryResponseInterpreterImpl} translates the facet and values to
 * {@link Dimension} and {@link DimensionValue}
 * 
 * Given that the {@link DimensionConfigWrapper} is always static in
 * SolrDimension, two {@link Map}s are used for
 * 
 * "caching" the mapping of {@link DimensionConfig} ids to
 * {@link DimensionConfig} instances and the mapping of
 * {@link AbstractDimensionValueConfig} ids to
 * {@link AbstractDimensionValueConfig} instances
 * 
 * @author mhc
 * 
 */
public class QueryResponseInterpreterImpl implements QueryResponseInterpreter {

    private Map<String, DimensionConfig> dimensionConfigMap = null;

    private Map<String, AbstractDimensionValueConfig> dimensionValueConfigMap = null;

    private static final Logger logger = LoggerFactory.getLogger(QueryResponseInterpreterImpl.class);

    @SuppressWarnings("unchecked")
    public DimensionResponse interpretQueryResponse(QueryResponse solrQueryResponse, DimensionConfigWrapper config) throws SolrDimensionException {

        if (dimensionConfigMap == null || dimensionValueConfigMap == null) {
            initMaps(config);
        }

        DimensionResponse solrDimensionResponse = new DimensionResponse();

        // get all facet fields
        List<FacetField> facetFields = solrQueryResponse.getFacetFields();

        // get params to get facet filter queries
        SimpleOrderedMap<String> params = (SimpleOrderedMap<String>) solrQueryResponse.getHeader().get(StaticConfiguration.SOLR_RESPONSE_PARAMS);
        
        List<String> activeFilterQueries = getFilterQueries(params.get(StaticConfiguration.SOLR_RESPONSE_FILTER_QUERY));

        SimpleOrderedMap<String> activeFacetPrefixes = QueryUtil.getFacetPrefixFromQueryResponse(solrQueryResponse);
        
        List<Dimension> selectedDimensions = getSelectedDimensions(activeFilterQueries);
        solrDimensionResponse.setSelectedDimensions(selectedDimensions);
        
        for (FacetField facetField : facetFields) {

            List<Count> counts = facetField.getValues();
            Map<String, DimensionValue> dimensionValues = new LinkedHashMap<String, DimensionValue>();
            long dimensionCount = 0;

            if (counts != null && !counts.isEmpty()) {

                for (Count count : counts) {
                    
                    //FIXME: SolrJ returns the filters using ClientUtils.escapeQueryChars(string) method
                    //this has result in characters such as space, pipe, etc escaped by the java "\\" 
                    //escape char.... However, when getting the filters from the QueryResponse directly
                    //this won't happened, so this is to "un-escape" the escaped chars for matching 
                    //purpose
                    String filterQuery = count.getAsFilterQuery().replace("\\", "");
                    
                    // No point displaying the filters already selected
                    if (count.getCount() > 0 && !activeFilterQueries.contains(filterQuery)) {
                   // if (count.getCount() > 0){
                        String dimensionValueName = null;
                        String facetPrefixQuery = null;
                        // only check for prefixes if there is a Prefix
                        // separator
                        if (count.getName().contains(StaticConfiguration.SEPARATOR)) {

                            logger.debug("Facet value: " + count.getName() + " contains prefix");

                            if (activeFacetPrefixes != null && activeFacetPrefixes.size() > 0) {

                                // check if the field is prefixed
                                if (activeFacetPrefixes.get(facetField.getName()) != null && count.getName().startsWith(activeFacetPrefixes.get(facetField.getName()))) {

                                    logger.debug("Facet prefix found for field: " + facetField.getName() + " and matches facet value: " + count.getName());

                                    // Cut out the prefix, since it is selected
                                    dimensionValueName = count.getName().split(StaticConfiguration.SEPARATOR_REGEX)[1];

                                }

                            }

                            // After checking the prefixes, if the
                            // dimensionValueName is still null, this probably
                            // means the active facet prefixes is not on this
                            // facet value, so ONLY display the first part
                            // And we need to retrieve the display name from the
                            // dimensionConfig
                            if (dimensionValueName == null) {
                                String prefix = count.getName().split(StaticConfiguration.SEPARATOR_REGEX)[0];
                                facetPrefixQuery = facetField.getName() + StaticConfiguration.COLON + prefix;
                                dimensionValueName = getDimensionValueDisplayName(prefix);
                            }

                        } else {
                            dimensionValueName = getDimensionValueDisplayName(count.getName());
                        }

                        dimensionCount++;

                        // This only occurs for sift dimension, since no
                        // DimensionValue can have the same name
                        // if there is a same name, this is a prefix
                        if (dimensionValues.containsKey(dimensionValueName)) {

                            DimensionValue sameNameDimVal = dimensionValues.get(dimensionValueName);

                            sameNameDimVal.setCount(sameNameDimVal.getCount() + count.getCount());

                        } else {
                            //FIXME - SOLR is doing something really funny here, you would have thought having a facet
                            //prefix query would limit the result set. But in reality, we also need a filter query 
                            if (facetPrefixQuery != null){
                                filterQuery = facetPrefixQuery + QueryParamConstants.WILD_CARD;
                            }
                            
                            DimensionValue dimensionValue = new DimensionValue(dimensionValueName, filterQuery, facetPrefixQuery, count.getCount());
                            dimensionValues.put(dimensionValueName, dimensionValue);

                        }

                    } 

                }

            }

            // only create a new dimension and add it to solrDimensionResponse
            // if there are values in it.

            if (!dimensionValues.isEmpty()) {

                Dimension dimension = new Dimension(getDimensionDisplayName(facetField.getName()), dimensionCount);

                if ((dimensionConfigMap.get(facetField.getName()) != null) && DimensionConfig.ALPHA_VALUE_ORDER.equals(dimensionConfigMap.get(facetField.getName()).getValueOrder())) {

                    ArrayList<DimensionValue> dimVals = new ArrayList<DimensionValue>(dimensionValues.values());
                    // sort alpha
                    Collections.sort(dimVals);

                    dimension.setDimensionValues(dimVals);

                } else {
                    dimension.setDimensionValues(new ArrayList<DimensionValue>(dimensionValues.values()));
                }

                solrDimensionResponse.addDimension(dimension);

            }

        }

        solrDimensionResponse.setSolrResponse(solrQueryResponse);

        return solrDimensionResponse;
    }

    
    /**
     * Create a list of selected dimensions from the FacetQueries and FacetPrefixes indicated active in the query
     * response.
     * 
     * @param activeFilterQueries
     *          A {@link List} of String type of all active filters
     * @return
     */
    private List<Dimension> getSelectedDimensions(List<String> activeFilterQueries) {
        
        List<Dimension> selectedDimensions =  null;
        
        if (activeFilterQueries != null && !activeFilterQueries.isEmpty()){
            
            if (selectedDimensions == null){
                selectedDimensions = new ArrayList<Dimension>();
            }
            
            for (String filterQuery : activeFilterQueries){
                
                String[] facetFieldNameAndValue = QueryUtil.extractFieldAndValue(filterQuery);
                
                if (facetFieldNameAndValue != null && facetFieldNameAndValue.length == 2){
                    
                    logger.debug("MTD: [getSelectedDimensions] " + facetFieldNameAndValue +" will be processed for Selected dimensions");
                    
                    if (dimensionConfigMap.containsKey(facetFieldNameAndValue[0])){
                        //TODO: it is assume that there is no multi select, logic here should be changed if SolrDimension support this
                        Dimension dimension = new Dimension(dimensionConfigMap.get(facetFieldNameAndValue[0]).getDisplayName(), 0);
                        
                        String dimValName = null;
                        
                        String dimPrefixName = null;
                        
                        String facetPrefixQuery = null;
                        
                        
                        if (facetFieldNameAndValue[1].contains(StaticConfiguration.SEPARATOR)){
                            //we have a prefixable facet value. Now that it is selected as filter, need to populate both the fist and second part
                            String[] dimvalWithPrefix = facetFieldNameAndValue[1].split(StaticConfiguration.SEPARATOR_REGEX);
                            
                            dimValName = dimvalWithPrefix[1];
                            dimPrefixName = getDimensionValueDisplayName(dimvalWithPrefix[0]);
                            facetPrefixQuery = facetFieldNameAndValue[0] + StaticConfiguration.COLON + dimvalWithPrefix[0];
                            
                            
                        } else {
                           
                            //Wildcard will ONLY happen to facetPrefixes we need to strip that out, also set filterQuery to null to indicate this is a prefix
                            if (facetFieldNameAndValue[1].endsWith(QueryParamConstants.WILD_CARD)) {
                                filterQuery = null;
                                facetFieldNameAndValue[1] = facetFieldNameAndValue[1].replace(QueryParamConstants.WILD_CARD, StringUtils.EMPTY);
                                facetPrefixQuery = facetFieldNameAndValue[0] + StaticConfiguration.COLON + facetFieldNameAndValue[1];

                            }
                            
                            // There are two types of dimension value, defined,
                            // i.e. Manual dimensions, and un-defined, i.e.
                            // default
                            // we need to cater for both cases
                            if (dimensionValueConfigMap.containsKey(facetFieldNameAndValue[1])) {

                                AbstractDimensionValueConfig dimValConfig = dimensionValueConfigMap.get(facetFieldNameAndValue[1]);
                                dimValName = dimValConfig.getDisplayName();

                            } else {

                                // a default DimensionValue generated by Solr

                                dimValName = facetFieldNameAndValue[1];

                            }

                        }
                        
                        
                        dimension.setDimensionValues(Collections.singletonList(new DimensionValue(dimValName, filterQuery, facetPrefixQuery, 0, dimPrefixName)));
                        
                        selectedDimensions.add(dimension);
                        
                    }
                    
                }
                
               
                
            }
            
            
        }
        
        
        return selectedDimensions;
    }

    /**
     * This is for filtering out facet values already selected, since the param
     * can be a list or array list, using object would be the safest
     **/
    @SuppressWarnings("unchecked")
    private List<String> getFilterQueries(Object filter) {

        List<String> result = null;

        if (filter != null) {

            if (filter instanceof String) {
                logger.debug("Filter is String type.... ONLY One filter selected.");

                result = Collections.singletonList(filter.toString());
            } else {
                logger.debug("Filter is List type.... multiple filters selected");
                result = (List<String>) filter;

            }

        } else {
            
            result = Collections.emptyList();
            
        }

        return result;

    }

    private void initMaps(DimensionConfigWrapper config) {

        List<DimensionConfig> dimensionConfigList = config.getDimensionConfigs();

        dimensionConfigMap = new HashMap<String, DimensionConfig>();
        dimensionValueConfigMap = new HashMap<String, AbstractDimensionValueConfig>();

        for (DimensionConfig dimensionConfig : dimensionConfigList) {

            // DimensionConfigId has different prefix, need to get the first
            // instance of AbstractDimensionValueConfig
            // To determine the Prefix type
            String dimensionConfigId = null;

            if (dimensionConfig.getDimensionValueConfigs() != null) {

                for (AbstractDimensionValueConfig dimensionValueConfig : dimensionConfig.getDimensionValueConfigs()) {

                    if (dimensionConfigId == null) {

                        dimensionConfigId = dimensionConfig.getId();

                        if (dimensionValueConfig instanceof ExactDimensionValueConfig) {

                            dimensionConfigId = StaticConfiguration.FIELD_PREFIX + dimensionConfigId;

                        } else {

                            dimensionConfigId = StaticConfiguration.MULTI_VALUED_FIELD_PREFIX + dimensionConfigId;

                        }

                    }

                    dimensionValueConfigMap.put(dimensionValueConfig.getDimensionValueId(), dimensionValueConfig);
                }
            } else {

                // this is for dimensionConfig in "default" mode

                dimensionConfigId = dimensionConfig.getId();
            }

            dimensionConfigMap.put(dimensionConfigId, dimensionConfig);

        }

    }

    private String getDimensionDisplayName(String facetFieldName) {

        String dimensionName = null;

        if (dimensionConfigMap.get(facetFieldName) != null) {
            dimensionName = dimensionConfigMap.get(facetFieldName).getDisplayName();
        } else {
            dimensionName = facetFieldName;
        }

        return dimensionName;

    }

    private String getDimensionValueDisplayName(String facetValue) {

        String dimensionValueDisplayName = null;

        if (dimensionValueConfigMap.get(facetValue) != null) {
            dimensionValueDisplayName = dimensionValueConfigMap.get(facetValue).getDisplayName();
        } else {
            dimensionValueDisplayName = facetValue;

        }

        return dimensionValueDisplayName;

    }

}
